<?php require_once 'PEAR.php';
require_once '../yubico-php-lib/yubikey.php';

define('DEVICE_ID_LEN', 12);
define('CLICK2LOGIN', 'One touch on the YubiKey button to log in');
define('ONECLICK', 'One touch on the YubiKey button to generate the one-time password');

/**
 * Class for verifying Yubico One-Time-Passcodes
 *
 * Simple example:
 * <code>
 * $yubi = &new Auth_Yubico('42');
 * $auth = $yubi->verify("ccbbddeertkrctjkkcglfndnlihhnvekchkcctif");
 * if (PEAR::isError($auth)) {
 *    print "<p>Authentication failed: " . $auth->getMessage();
 *    print "<p>Debug output from server: " . $yubi->getLastResponse();
 * } else {
 *    print "<p>You are authenticated!";
 * }
 * </code>
 */
class Auth_Yubico
{
	/**#@+
	 * @access private
	 */

	/**
	 * Yubico client ID
	 * @var string
	 */
	var $_id;

	/**
	 * Yubico client key
	 * @var string
	 */
	var $_key;

	/**
	 * Response from server
	 * @var string
	 */
	var $_response;

	/**
	 * Constructor
	 *
	 * Sets up the object
	 * @param    string  The client identity
	 * @param    string  The client MAC key (optional)
	 * @access public
	 */
	function Auth_Yubico($id, $key = '')
	{
		$this->_id =  $id;
		$this->_key = $key;
	}

	/**
	 * Return the last data received from the server, if any.
	 *
	 * @return string		Output from server.
	 * @access public
	 */
	function getLastResponse()
	{
		return $this->_response;
	}

	/* TODO? Add functions to get parsed parts of server response? */

	/**
	 * Verify Yubico OTP
	 *
	 * @param string $token     Yubico OTP
	 * @return mixed            PEAR error on error, true otherwise
	 * @access public
	 */
	function verify($token)
	{
		/* TODO: Generate signature here. */

		/* TODO: Support https. */

		$url = "http://api.yubico.com/wsapi/verify?id=" .
			$this->_id . "&otp=" . $token;

		$ch = curl_init($url);
		curl_setopt($ch, CURLOPT_USERAGENT, "PEAR Auth_Yubico");
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		$this->_response = curl_exec($ch);
		curl_close($ch);

		/* TODO: Verify signature here. */

		if(!preg_match("/status=([a-zA-Z0-9_]+)/", $this->_response, $out)) {
			return PEAR::raiseError('Could not parse response');
		}

		$status = $out[1];

		if ($status != 'OK') {
			return PEAR::raiseError($status);
		}

		return true;
	}
} // End class Auth_Yubico


function verifyYubikeyOtp($otp, $id=28) {
	//$otp = "bglhrljiliggfltrergeetberjffbletvidhjngvrdrk";
	$otp = strtolower($otp);
	$yubi = &new Auth_Yubico($id); # 28 is ML id, 27 is IPTV id
	$auth = $yubi->verify($otp);
	$err = '';
	if (PEAR::isError($auth)) {
   		$err = "Yubikey authentication failed: " . $auth->getMessage();
			//"<p>" . $yubi->getLastResponse();
	}
	return $err;
}

function modhexToB64($modhex_str) {
	$s = ModHex::Decode($modhex_str);
	return base64_encode($s);
}

function b64ToModhex($b64_str) {
	$s = base64_decode($b64_str);
	return ModHex::Encode($s);
}

function b64ToHex($b64_str) {
	$s = '';
	$tid = base64_decode($b64_str);
	$a = str_split($tid);
	for ($i=0; $i < count($a); $i++) {
		$s .= dechex(ord($a[$i]));
	}
	return $s;
}

// $devId: The first 12 chars from the OTP in modhex
function getKeyInfo($devId) {
	$a = array();
	$a['usrid'] = -1;
	$devId = strtolower($devId);
	$tokenId = modhexToB64($devId);
	
	// Get key info
	$stmt = 'SELECT y.id, y.client_id,y.active, c.notes, c.perm_id, c.email'.
		' FROM yubikeys y, clients c WHERE tokenId='.mysql_quote($tokenId).
		' AND y.client_id=c.id';

	writeLog('# getAdmInfoByKey:'.$stmt, true);
	$r = query($stmt);
	if (mysql_num_rows($r) > 0) {
		$row = mysql_fetch_assoc($r);
		mysql_free_result($r);
		$a['client'] = $row['client_id'];
		$a['keyid'] = $row['id'];
		$a['perm'] = $row['perm_id'];
		$a['active'] = $row['active'];
		$a['email'] = $row['email'];
		$a['note'] = $row['notes'];	
		$a['usrid'] = 0; // New key
		
		// Get admin info, if it associates with an admin already, if so, mark usrid
		$stmt = 'SELECT a.id, a.pin FROM yubikeys y, clients c, admin a WHERE tokenId='.mysql_quote($tokenId).
			' AND y.id=a.keyid AND c.id=y.client_id ';
		$r = query($stmt);
		if (mysql_num_rows($r) > 0) {
			$row = mysql_fetch_assoc($r);
			mysql_free_result($r);
			$a['usrid'] = $row['id'];
			$a['pin'] = aesDecrypt($row['pin']);
		}
	}
	return $a;
} // End getKeyInfo

// $devId: The first 12 chars from the OTP
function getAuthData($devId) {
	$tokenId = modhexToB64($devId);
	$stmt = 'SELECT y.id,y.client_id,y.secret,y.active,y.counter,y.low,y.high,'.
		'c.active as c_active,c.secret as c_secret,c.chk_sig,c.chk_owner'.
		' FROM yubikeys y, clients c WHERE tokenId='.mysql_quote($tokenId).
		' AND y.client_id=c.id';
	$r = query($stmt);
	if (mysql_num_rows($r) > 0) {
		$row = mysql_fetch_assoc($r);
		mysql_free_result($r);
		return $row;
	}
	return null;
} // End getAuthData

// Return an array of full yubikey info
function getKeyInfoById($keyId, $client) {
	$a = array();
	$stmt = 'SELECT * FROM yubikeys WHERE id='.$keyId.' AND client_id='.$client;
	$r = query($stmt);
	if ($row=mysql_fetch_assoc($r)) {
		mysql_free_result($r);
		$a = array_merge($a, $row);
	}	
	return $a;
}

function addClient($email, $secret, $perm, $notes) {
	global $admEmail;
	
	$client = -1;
	
	$stmt = 'INSERT INTO clients (perm_id, active, email, secret, notes, created) VALUES ('.
		$perm.','.
		'1,'.
		mysql_quote($email).','.
		mysql_quote($secret).','.
		mysql_quote($notes).','.
		'NOW())';
	
	if (!query($stmt)) {
		$err = 'Failed to add client email='.$email;
		writeLog($err);
		sendMail($admEmail, $err, $stmt, $admEmail);		
	} else {
		$client= mysql_insert_id();
	}
	return $client;
}

function delClient($id) {
	global $admEmail;
	$stmt = 'DELETE FROM clients WHERE id='.$id;	
	if (!query($stmt)) {
		$err = 'Failed to del client id: '.$id;
		writeLog($err);
		sendMail($admEmail, 'Failed to del client', $err, $admEmail);
		return false;
	}
	return true;
}

function delAdmin($id) {
	global $admEmail;
	$stmt = 'DELETE FROM admin WHERE id='.$id;	
	if (!query($stmt)) {
		$err = 'Failed to del admin id: '.$id;
		writeLog($err);
		sendMail($admEmail, 'Failed to del adm', $err, $admEmail);
		return false;
	}
	return true;
}

function delAdminByKeyId($keyid) {
	global $admEmail;
	$stmt = 'DELETE FROM admin WHERE keyid='.$keyid;
	if (!query($stmt)) {
		$err = 'Failed to del admin w/ keyid: '.$keyid;
		writeLog($err);
		sendMail($admEmail, 'Failed to del yubikey', $err, $admEmail);
		return false;
	}
	return true;
}

function updYubikey($usrid, $id, $devId, $pin, $note) {
	global $admEmail;
	$stmt = 'UPDATE admin SET pin='.mysql_quote(aesEncrypt($pin)).
		',note='.mysql_quote($note).
		' WHERE id='.$id;
	if (!query($stmt)) {
		$err = 'Failed to upd Yubikey, devId='.$devId;
		writeLog($err);
		sendMail($admEmail, 'Failed to del yubikey', $err, $admEmail);
		return false;
	}
	return true;
}

function updClientOfKey($keyid, $client) {
	$stmt = 'UPDATE yubikeys SET client_id='.$client.' WHERE id='.$keyid;
	if (!query($stmt)) {
		$err = 'Failed to upd key owner in tbl yubikeys, id='.$keyid;
		writeLog($err);
		return false;
	}
	return true;
}

function nextSn() {
	return time();
}

function addNewKey($devId64, $active, $sec, $note, $client) {
	global $admEmail;
	$sn = nextSn();
	$stmt = 'INSERT INTO yubikeys '.
	  '(client_id,active,created,accessed,tokenId,userId,secret,counter,low,high,notes,serial) VALUES ('.
		$client.','.
		$active.','.
		'NOW(),'.
		'NOW(),'.
		mysql_quote($devId64).','.
		mysql_quote($devId64).','.
		mysql_quote($sec).','.
		'0,'.
		'0,'.
		'0,'.
		mysql_quote($note).','.
		mysql_quote($sn).
		')';
	if (!query($stmt)) {
		$err = 'Failed to add a new Yubikey, devId='.$devId64.' for client '.$client;
		writeLog($err);
		sendMail($admEmail, 'Failed to add a new yubikey', $err, $admEmail);
		return null;
	}
	$a = array();
	$a['keyid'] = mysql_insert_id();
	$a['sn'] = $sn;
	return $a;
}

function addNewAdmKeyAndPin($keyId, $pin, $devId, $note, $client) {
	global $admEmail;
	$stmt = 'INSERT INTO admin (keyid,pin,note,ip,last_access,client,creation) VALUES ('.
		$keyId.','.
		mysql_quote(aesEncrypt($pin)).','.
		mysql_quote($note).','.
		mysql_quote($_SERVER['REMOTE_ADDR']).','.
		'NOW(),'.
		$client.','.
		'NOW())';
	if (!query($stmt)) {
		$err = 'Failed to add a new Yubikey, devId='.$devId.' for user '.$_SESSION['usrid'];
		writeLog($err);
		sendMail($admEmail, 'Failed to add a new yubikey', $err, $admEmail);
		return false;
	}
	
	return true;
}

function isAdmKey($keyid) {
    $stmt = 'SELECT id FROM admin WHERE keyid='.$keyid;	
    $r = query($stmt);
    $n = mysql_num_rows($r);
    mysql_free_result($r);
	return $n;
}

function numOfAdmins() {
    $stmt = 'SELECT COUNT(*) as C FROM admin WHERE client='.$_SESSION['client'];	
    $r = query($stmt);
    if ($row=mysql_fetch_assoc($r)) {
    	$n = $row['C'];
    	mysql_free_result($r);
    } else {
    	$n = 0;
    }
    return $n;
}

// Return the client id. If not found, returns -1
// Key ID is the id in Yubikey table
function clientOfYubikey($keyId) {
	$stmt = 'SELECT client_id AS C FROM yubikeys y WHERE id='.$keyId;
	$r = query($stmt);
	if ($row=mysql_fetch_assoc($r)) {
    	$c = $row['C'];
    	mysql_free_result($r);
    } else {
    	$c = -1;
    }
    return $c;	
}
?>
